/*
 * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Codename One designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *  
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 * 
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 * 
 * Please contact Codename One through http://www.codenameone.com/ if you 
 * need additional information or have any questions.
 */
package com.codename1.impl.javase;

import com.codename1.ui.Component;
import com.codename1.ui.Display;
import com.codename1.ui.Image;
import com.codename1.ui.Label;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.font.FontRenderContext;
import java.awt.font.LineBreakMeasurer;
import java.awt.font.TextLayout;
import java.awt.image.BufferedImage;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.text.AttributedCharacterIterator;
import java.text.AttributedString;
import java.text.BreakIterator;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.swing.AbstractAction;
import javax.swing.AbstractCellEditor;
import javax.swing.Action;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JOptionPane;
import javax.swing.JTable;
import javax.swing.JTree;
import javax.swing.UIManager;
import javax.swing.border.Border;
import javax.swing.border.LineBorder;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumnModel;
import javax.swing.table.TableModel;
import javax.swing.table.TableRowSorter;
import javax.swing.tree.DefaultTreeCellRenderer;

/**
 * The Codename One performance monitor shows you how long (in nano-seconds) it took to paint a specific
 *
 * @author Shai Almog
 */
public class PerformanceMonitor extends javax.swing.JFrame {
    private int componentId = 0;
    private static String idString = "$prefid";
    private Map<Integer, Stats> componentStats = new HashMap<Integer, Stats>();

    private DefaultTableModel trackedDrawing;
    private boolean trackDrawing;
    
    private boolean paused;
    private static final String[] COLUMNS = {
        "Name", "Type", "UIID", "Parent Name", "Icon", "Invocations", "Fastest", "Slowest", "Average"        
    };
    private static final Class[] COLUMN_CLASSES = {
        String.class, String.class, String.class, String.class, String.class, Integer.class, Long.class, Long.class, Long.class
    };

    private int imageRam;
    
    /** Creates new form PerformanceMonitor */
    public PerformanceMonitor() {
        initComponents();
        if(Display.getInstance().getCurrent() != null) {
            refreshFrameActionPerformed(null);
        }
        resultData.setModel(new Model());
        performanceLog.setLineWrap(true);
        resultData.setRowSorter(new TableRowSorter<Model>((Model)resultData.getModel()));
    }
    
    public void addImageRAM(int ram) {
        imageRam += ram;
        imageMemory.setText("Image Memory Overhead: " + imageRam);
    }

    public void removeImageRAM(int ram) {
        imageRam -= ram;
        imageMemory.setText("Image Memory Overhead: " + imageRam);
    }

    /** This method is called from within the constructor to
     * initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        jScrollPane2 = new javax.swing.JScrollPane();
        jTable1 = new javax.swing.JTable();
        jTabbedPane1 = new javax.swing.JTabbedPane();
        jPanel1 = new javax.swing.JPanel();
        jScrollPane1 = new javax.swing.JScrollPane();
        resultData = new javax.swing.JTable();
        clearData = new javax.swing.JButton();
        pauseContinue = new javax.swing.JButton();
        jLabel1 = new javax.swing.JLabel();
        jLabel2 = new javax.swing.JLabel();
        jScrollPane3 = new javax.swing.JScrollPane();
        performanceLog = new javax.swing.JTextArea();
        imageMemory = new javax.swing.JLabel();
        runGC = new javax.swing.JButton();
        jPanel2 = new javax.swing.JPanel();
        jSplitPane1 = new javax.swing.JSplitPane();
        jScrollPane4 = new javax.swing.JScrollPane();
        componentHierarchy = new javax.swing.JTree();
        jScrollPane5 = new javax.swing.JScrollPane();
        renderedItems = createJTable();
        jToolBar1 = new javax.swing.JToolBar();
        refreshFrame = new javax.swing.JButton();

        FormListener formListener = new FormListener();

        jTable1.setModel(new javax.swing.table.DefaultTableModel(
            new Object [][] {
                {null, null, null, null},
                {null, null, null, null},
                {null, null, null, null},
                {null, null, null, null}
            },
            new String [] {
                "Title 1", "Title 2", "Title 3", "Title 4"
            }
        ));
        jScrollPane2.setViewportView(jTable1);

        setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
        setTitle("Performance Monitor");
        addWindowListener(formListener);

        jScrollPane1.setViewportView(resultData);

        clearData.setText("Clear Data");
        clearData.addActionListener(formListener);

        pauseContinue.setText("Pause/Continue");
        pauseContinue.addActionListener(formListener);

        jLabel1.setText("Component Details (times are in nano-seconds: one billionth of a second)");

        jLabel2.setText("Log");

        performanceLog.setColumns(20);
        performanceLog.setRows(5);
        jScrollPane3.setViewportView(performanceLog);

        imageMemory.setText("Image Memory Overhead: (Calculating...)");

        runGC.setText("GC");
        runGC.addActionListener(formListener);

        javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1);
        jPanel1.setLayout(jPanel1Layout);
        jPanel1Layout.setHorizontalGroup(
            jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(jPanel1Layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
                    .addComponent(jScrollPane3, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, 793, Short.MAX_VALUE)
                    .addComponent(jScrollPane1, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, 793, Short.MAX_VALUE)
                    .addGroup(javax.swing.GroupLayout.Alignment.LEADING, jPanel1Layout.createSequentialGroup()
                        .addComponent(clearData)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                        .addComponent(pauseContinue)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 89, Short.MAX_VALUE)
                        .addComponent(runGC)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(imageMemory))
                    .addComponent(jLabel1, javax.swing.GroupLayout.Alignment.LEADING)
                    .addComponent(jLabel2, javax.swing.GroupLayout.Alignment.LEADING))
                .addContainerGap())
        );

        jPanel1Layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {clearData, pauseContinue, runGC});

        jPanel1Layout.setVerticalGroup(
            jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(jPanel1Layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(clearData)
                    .addComponent(pauseContinue)
                    .addComponent(imageMemory)
                    .addComponent(runGC))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(jLabel1)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 170, Short.MAX_VALUE)
                .addGap(18, 18, 18)
                .addComponent(jLabel2)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(jScrollPane3, javax.swing.GroupLayout.DEFAULT_SIZE, 137, Short.MAX_VALUE)
                .addContainerGap())
        );

        jTabbedPane1.addTab("Generic Statistics", jPanel1);

        componentHierarchy.addTreeSelectionListener(formListener);
        jScrollPane4.setViewportView(componentHierarchy);

        jSplitPane1.setLeftComponent(jScrollPane4);

        renderedItems.setModel(new javax.swing.table.DefaultTableModel(
            new Object [][] {
                {null, null, null, null},
                {null, null, null, null},
                {null, null, null, null},
                {null, null, null, null}
            },
            new String [] {
                "Title 1", "Title 2", "Title 3", "Title 4"
            }
        ));
        jScrollPane5.setViewportView(renderedItems);

        jSplitPane1.setRightComponent(jScrollPane5);

        jToolBar1.setRollover(true);

        refreshFrame.setText("Refresh");
        refreshFrame.setFocusable(false);
        refreshFrame.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
        refreshFrame.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
        refreshFrame.addActionListener(formListener);
        jToolBar1.add(refreshFrame);

        javax.swing.GroupLayout jPanel2Layout = new javax.swing.GroupLayout(jPanel2);
        jPanel2.setLayout(jPanel2Layout);
        jPanel2Layout.setHorizontalGroup(
            jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addComponent(jSplitPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 805, Short.MAX_VALUE)
            .addComponent(jToolBar1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
        );
        jPanel2Layout.setVerticalGroup(
            jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel2Layout.createSequentialGroup()
                .addComponent(jToolBar1, javax.swing.GroupLayout.PREFERRED_SIZE, 25, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(jSplitPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 385, Short.MAX_VALUE))
        );

        jTabbedPane1.addTab("Rendering Details", jPanel2);

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
        getContentPane().setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addComponent(jTabbedPane1)
                .addContainerGap())
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addComponent(jTabbedPane1)
                .addContainerGap())
        );

        pack();
    }

    // Code for dispatching events from components to event handlers.

    private class FormListener implements java.awt.event.ActionListener, java.awt.event.WindowListener, javax.swing.event.TreeSelectionListener {
        FormListener() {}
        public void actionPerformed(java.awt.event.ActionEvent evt) {
            if (evt.getSource() == clearData) {
                PerformanceMonitor.this.clearDataActionPerformed(evt);
            }
            else if (evt.getSource() == pauseContinue) {
                PerformanceMonitor.this.pauseContinueActionPerformed(evt);
            }
            else if (evt.getSource() == runGC) {
                PerformanceMonitor.this.runGCActionPerformed(evt);
            }
            else if (evt.getSource() == refreshFrame) {
                PerformanceMonitor.this.refreshFrameActionPerformed(evt);
            }
        }

        public void windowActivated(java.awt.event.WindowEvent evt) {
        }

        public void windowClosed(java.awt.event.WindowEvent evt) {
        }

        public void windowClosing(java.awt.event.WindowEvent evt) {
            if (evt.getSource() == PerformanceMonitor.this) {
                PerformanceMonitor.this.formWindowClosing(evt);
            }
        }

        public void windowDeactivated(java.awt.event.WindowEvent evt) {
        }

        public void windowDeiconified(java.awt.event.WindowEvent evt) {
        }

        public void windowIconified(java.awt.event.WindowEvent evt) {
        }

        public void windowOpened(java.awt.event.WindowEvent evt) {
        }

        public void valueChanged(javax.swing.event.TreeSelectionEvent evt) {
            if (evt.getSource() == componentHierarchy) {
                PerformanceMonitor.this.componentHierarchyValueChanged(evt);
            }
        }
    }// </editor-fold>//GEN-END:initComponents

    public void printToLog(String t) {
        performanceLog.append(t + "\n");
        performanceLog.setCaretPosition(t.length());
    }   
    
    private void pauseContinueActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_pauseContinueActionPerformed
        paused = !paused;
    }//GEN-LAST:event_pauseContinueActionPerformed

    private void clearDataActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_clearDataActionPerformed
        ((Model)resultData.getModel()).clear();
        performanceLog.setText("");
    }//GEN-LAST:event_clearDataActionPerformed

    private void formWindowClosing(java.awt.event.WindowEvent evt) {//GEN-FIRST:event_formWindowClosing
        JavaSEPort.disablePerformanceMonitor();
    }//GEN-LAST:event_formWindowClosing

    private void runGCActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_runGCActionPerformed
        System.gc(); System.gc();
    }//GEN-LAST:event_runGCActionPerformed

    private void refreshFrameActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_refreshFrameActionPerformed
        componentHierarchy.setModel(new ComponentTreeModel(Display.getInstance().getCurrent()));
        componentHierarchy.setCellRenderer(new DefaultTreeCellRenderer() {

            @Override
            public java.awt.Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus) {
                String s = value.toString();
                if(value instanceof Component) {
                    s = ((Component)value).getUIID() + ": " + s;
                }
                return super.getTreeCellRendererComponent(tree, s, sel, expanded, leaf, row, hasFocus); //To change body of generated methods, choose Tools | Templates.
            }
            
        });
        
        Display.getInstance().callSerially(new Runnable() {
            public void run() {
                trackDrawing = true;
                Display.getInstance().getCurrent().repaint();
                Display.getInstance().callSerially(new Runnable() {
                    public void run() {
                        // data collected
                        trackDrawing = false;
                        renderedItems.setModel(createTableModel());
                    }
                });
            }
        });
    }//GEN-LAST:event_refreshFrameActionPerformed
    
    private void refreshComponentStatsTable(Component c) {
        TableModel tm = (TableModel)c.getClientProperty("track");
        if(tm != null) {
            renderedItems.setModel(tm);
        } else {
            renderedItems.setModel(createTableModel());
        }
    }
    
    private void componentHierarchyValueChanged(javax.swing.event.TreeSelectionEvent evt) {//GEN-FIRST:event_componentHierarchyValueChanged
        if(evt.getPath() != null) {
            Object o = evt.getPath().getLastPathComponent();
            if(o instanceof Component) {
                Component c = (Component)o;
                refreshComponentStatsTable(c);
            }
        }
    }//GEN-LAST:event_componentHierarchyValueChanged

    public void nothingWithinComponentPaint(Component c) {
        if(trackDrawing) {
            if(c.isCellRenderer()) {
                return;
            }
            if(c.getParent() != null) {
                trackedDrawing = (DefaultTableModel)c.getParent().getClientProperty("track");
            }
        }
    }
    
    private static DefaultTableModel createTableModel() {
        return new DefaultTableModel(new String[]{"Call", "Details", "Comments", "Stack", "Image"}, 0) {

                @Override
                public Class<?> getColumnClass(int columnIndex) {
                    if(columnIndex == 3) {
                        return Action.class;
                    }
                    if(columnIndex == 4) {
                        return Icon.class;
                    }
                    return String.class;
                }
                
            };
    }
    
    public void beforeComponentPaint(Component c) {
        if(trackDrawing) {
            if(c.isCellRenderer()) {
                return;
            }
            trackedDrawing = createTableModel();
            c.putClientProperty("track", trackedDrawing);
            return;
        }
        if(!paused) {
            c.putClientProperty("$t", new Long(System.nanoTime()));
        }
    }

    public void afterComponentPaint(Component c) {
        if(trackDrawing) {
            if(c.isCellRenderer()) {
                return;
            }
            if(c.getParent() != null) {
                trackedDrawing = (DefaultTableModel)c.getParent().getClientProperty("track");
            }
            return;
        }
        if(paused) {
            return;
        }
        long t = System.nanoTime();
        Long l = (Long)c.getClientProperty("$t");
        if(l != null) {
            t = t - l.longValue();
            Integer id = (Integer)c.getClientProperty(idString);
            Stats st;
            if(id == null) {
                id = new Integer(componentId);
                c.putClientProperty(idString, id);
                componentId++;
                st = new Stats(c);
                componentStats.put(id, st);                    
            } else {
                st = componentStats.get(id);
                if(st == null) {
                    st = new Stats(c);
                    componentStats.put(id, st); 
                }
            }
            st.updateInvocation(t);
        }
    }
    
    public static String getStackTrace(Throwable t) {
        StringWriter sw = new StringWriter();
        t.printStackTrace(new PrintWriter(sw));
        String s = sw.toString();
        int pos = s.indexOf("at");
        pos = s.indexOf("at", pos + 2);
        return s.substring(pos + 2);
    }
    
    public void setClip(int x, int y, int width, int height) {
        if(trackDrawing && trackedDrawing != null) {
            trackedDrawing.addRow(new Object[] {
                "setClip(" + x + ", " + y + ", " + width + ", " + height + ")",
                "",
                "",
                getStackTrace(new Throwable()),
                null
            });
        }
    }
    
    public void clipRect(int x, int y, int width, int height) {
        if(trackDrawing && trackedDrawing != null) {
            trackedDrawing.addRow(new Object[] {
                "clipRect(" + x + ", " + y + ", " + width + ", " + height + ")",
                "",
                "",
                getStackTrace(new Throwable()),
                null
            });
        }
    }

    public void drawLine(int x1, int y1, int x2, int y2) {
        if(trackDrawing && trackedDrawing != null) {
            trackedDrawing.addRow(new Object[] {
                "drawLine(" + x1 + ", " + y1 + ", " + x2 + ", " + y2 + ")",
                "",
                "",
                getStackTrace(new Throwable()),
                null
            });
        }
    }

    public void fillRect(int x, int y, int w, int h) {
        if(trackDrawing && trackedDrawing != null) {
            trackedDrawing.addRow(new Object[] {
                "fillRect(" + x + ", " + y + ", " + w + ", " + h + ")",
                "",
                "",
                getStackTrace(new Throwable()),
                null
            });
        }
    }
    
    void clearRect(int x, int y, int width, int height) {
        if(trackDrawing && trackedDrawing != null) {
            trackedDrawing.addRow(new Object[] {
                "clearRect(" + x + ", " + y + ", " + width + ", " + height + ")",
                "",
                "",
                getStackTrace(new Throwable()),
                null
            });
        }
    }
    
    public void drawRect(int x, int y, int width, int height) {
        if(trackDrawing && trackedDrawing != null) {
            trackedDrawing.addRow(new Object[] {
                "drawRect(" + x + ", " + y + ", " + width + ", " + height + ")",
                "",
                "",
                getStackTrace(new Throwable()),
                null
            });
        }
    }    
    
    public void drawRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight) {
        if(trackDrawing && trackedDrawing != null) {
            trackedDrawing.addRow(new Object[] {
                "drawRoundRect(" + x + ", " + y + ", " + width + ", " + height + ", " + arcWidth + ", " + arcHeight + ")",
                "",
                "",
                getStackTrace(new Throwable()),
                null
            });
        }
    }

    public void fillRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight) {
        if(trackDrawing && trackedDrawing != null) {
            trackedDrawing.addRow(new Object[] {
                "fillRoundRect(" + x + ", " + y + ", " + width + ", " + height + ", " + arcWidth + ", " + arcHeight + ")",
                "",
                "",
                getStackTrace(new Throwable()),
                null
            });
        }
    }

    public void fillArc(int x, int y, int width, int height, int startAngle, int arcAngle) {
        if(trackDrawing && trackedDrawing != null) {
            trackedDrawing.addRow(new Object[] {
                "fillArc(" + x + ", " + y + ", " + width + ", " + height + ", " + startAngle + ", " + arcAngle + ")",
                "",
                "",
                getStackTrace(new Throwable()),
                null
            });
        }
    }
    
    public void drawArc(int x, int y, int width, int height, int startAngle, int arcAngle) {
        if(trackDrawing && trackedDrawing != null) {
            trackedDrawing.addRow(new Object[] {
                "drawArc(" + x + ", " + y + ", " + width + ", " + height + ", " + startAngle + ", " + arcAngle + ")",
                "",
                "",
                getStackTrace(new Throwable()),
                null
            });
        }
    }
    
    public void setColor(int RGB) {
        if(trackDrawing && trackedDrawing != null) {
            trackedDrawing.addRow(new Object[] {
                "setColor(0x" + Integer.toHexString(RGB) + ")",
                "",
                "",
                getStackTrace(new Throwable()),
                null
            });
        }
    }
    
    public void setAlpha(int alpha) {
        if(trackDrawing && trackedDrawing != null) {
            trackedDrawing.addRow(new Object[] {
                "setAlpha(0x" + Integer.toHexString(alpha) + ")",
                "",
                "",
                getStackTrace(new Throwable()),
                null
            });
        }
    }
    
    public void drawString(String str, int x, int y) {
        if(trackDrawing && trackedDrawing != null) {
            trackedDrawing.addRow(new Object[] {
                "drawString(" + str + ", " + x + ", " + y + ")",
                "",
                "",
                getStackTrace(new Throwable()),
                null
            });
        }
    }
    
    public void drawImage(Object img, int x, int y) {
        if(trackDrawing && trackedDrawing != null) {
            trackedDrawing.addRow(new Object[] {
                "drawImage(" + x + ", " + y + ")",
                "Image size: " + ((BufferedImage) img).getWidth() + "x" + ((BufferedImage) img).getHeight(),
                "",
                getStackTrace(new Throwable()),
                new ImageIcon((BufferedImage)img)
            });
        }
    }

    public void drawImage(Object img, int x, int y, int w, int h) {
        if(trackDrawing && trackedDrawing != null) {
            trackedDrawing.addRow(new Object[] {
                "drawImage(" + x + ", " + y + ", " + w + ", " + h + ")",
                "Image size: " + ((BufferedImage) img).getWidth() + "x" + ((BufferedImage) img).getHeight(),
                "This version of the method is slow on feature phones",
                getStackTrace(new Throwable()),
                new ImageIcon((BufferedImage)img)
            });
        }
    }
    
    public void fillTriangle(int x1, int y1, int x2, int y2, int x3, int y3) {
        if(trackDrawing && trackedDrawing != null) {
            trackedDrawing.addRow(new Object[] {
                "fillTriangle(" + x1 + ", " + y1 + ", " + x2 + ", " + y2 + ", " + x3 + ", " + y3 + ")",
                "",
                "",
                getStackTrace(new Throwable()),
                null
            });
        }
    }
    
    public void drawRGB(int[] rgbData, int offset, int x, int y, int w, int h, boolean processAlpha) {
        if(trackDrawing && trackedDrawing != null) {
            trackedDrawing.addRow(new Object[] {
                "drawRGB(" + x + ", " + y + ", " + w + ", " + h + ")",
                "Rgb data length " + rgbData.length,
                "This method is problematic on some devices!",
                getStackTrace(new Throwable()),
                null
            });
        }
    }    
    
    public void stringWidth(Object nativeFont, String str) {
        if(trackDrawing && trackedDrawing != null) {
            trackedDrawing.addRow(new Object[] {
                "stringWidth(" + str + ")",
                "",
                "Slow method, don't overuse",
                getStackTrace(new Throwable()),
                null
            });
        }
    }    
    
    public void charWidth(Object nativeFont, char ch) {        
        if(trackDrawing && trackedDrawing != null) {
            trackedDrawing.addRow(new Object[] {
                "charWidth(" + ch + ")",
                "",
                "Slow method, don't overuse",
                getStackTrace(new Throwable()),
                null
            });
        }
    }

    
    
    static class Stats {
        private String name;
        private String type;
        private String uiid;
        private String parentName;
        private String imageName;
        private int invocationCount;
        private long fastest = Long.MAX_VALUE;
        private long slowest;
        private long[] allInvocations = new long[20];

        public Stats(Component c) {
            name = c.getName();
            type = c.getClass().getName();
            uiid = c.getUIID();
            if(c instanceof Label) {
                Image l = ((Label)c).getIcon();
                if(l != null) {
                    imageName = l.getImageName();
                }
            }
            if(c.getParent() != null) {
                parentName = c.getParent().getName();
            } 
        }
        
        public void updateInvocation(long t) {
            fastest = Math.min(t, fastest);
            slowest = Math.max(t, slowest);
            if(allInvocations.length <= invocationCount) {
                long[] arr = new long[allInvocations.length * 3];
                System.arraycopy(allInvocations, 0, arr, 0, allInvocations.length);
                allInvocations = arr;
            }
            allInvocations[invocationCount] = t;
            invocationCount++;
        }
        
        public long getAverage() {
            long total = 0;
            for(int iter = 0 ; iter < invocationCount ; iter++) {
                total += allInvocations[iter];
            }
            return total / invocationCount;
        }
        
        /**
         * @return the name
         */
        public String getName() {
            return name;
        }

        /**
         * @return the type
         */
        public String getType() {
            return type;
        }

        /**
         * @return the parentName
         */
        public String getParentName() {
            return parentName;
        }

        /**
         * @return the invocationCount
         */
        public int getInvocationCount() {
            return invocationCount;
        }

        /**
         * @return the fastest
         */
        public long getFastest() {
            return fastest;
        }

        /**
         * @return the slowest
         */
        public long getSlowest() {
            return slowest;
        }

        /**
         * @return the allInvocations
         */
        public long[] getAllInvocations() {
            return allInvocations;
        }

        /**
         * @return the uiid
         */
        public String getUiid() {
            return uiid;
        }

        /**
         * @return the imageName
         */
        public String getImageName() {
            return imageName;
        }
        
    }
    
    class Model implements TableModel {
        private List<TableModelListener> listeners = new ArrayList<TableModelListener>();

        @Override
        public int getRowCount() {
            return componentStats.size();
        }

        @Override
        public int getColumnCount() {
            return COLUMNS.length;
        }

        @Override
        public String getColumnName(int i) {
            return COLUMNS[i];
        }

        @Override
        public Class<?> getColumnClass(int i) {
            return COLUMN_CLASSES[i];
        }

        @Override
        public boolean isCellEditable(int i, int i1) {
            return false;
        }

        @Override
        public Object getValueAt(int row, int column) {
            Stats s = componentStats.get(row);
            switch(column) {
                case 0:
                    return s.getName();
                case 1:
                    return s.getType();
                case 2:
                    return s.getUiid();
                case 3:
                    return s.getParentName();
                case 4:
                    return s.getImageName();
                case 5:
                    return s.getInvocationCount();
                case 6:
                    return s.getFastest();
                case 7:
                    return s.getSlowest();
                default:
                    return s.getAverage();
            }
        }
        
        public void clear() {
            componentStats.clear();            
            componentId = 0;
            idString += "x";
            fireUpdate();
        }

        @Override
        public void setValueAt(Object o, int i, int i1) {
        }

        void fireUpdate() {
            for(TableModelListener t : listeners) {
                t.tableChanged(new TableModelEvent(this));
            }
        }

        private boolean first;
        @Override
        public void addTableModelListener(TableModelListener tl) {
            listeners.add(tl);
            if(!first) {
                first = true;
                javax.swing.Timer t = new javax.swing.Timer(2000, new ActionListener() {
                    @Override
                    public void actionPerformed(ActionEvent ae) {
                        if(!paused) {
                            fireUpdate();
                        }
                    }
                });
                t.setRepeats(true);
                t.start();
            }
        }

        @Override
        public void removeTableModelListener(TableModelListener tl) {
            listeners.remove(tl);
        }
    }
    
    private static JTable createJTable() {
        final JTable t = new JTable(createTableModel()) {    
            MultilineTableCell wordWrapRenderer = new MultilineTableCell();
            public TableCellRenderer getCellRenderer(int row, int column) {
                if (column < 3 ) {
                    return wordWrapRenderer;
                }
                else {
                    return super.getCellRenderer(row, column);
                }
            }
        };       
        Action view = new AbstractAction()
        {
            public void actionPerformed(ActionEvent e)
            {
                JTable table = (JTable)e.getSource();
                int modelRow = Integer.valueOf( e.getActionCommand() );
                String s = (String)((DefaultTableModel)table.getModel()).getValueAt(modelRow, 3);
                JOptionPane.showMessageDialog(t, s, "Stack", JOptionPane.INFORMATION_MESSAGE);
            }
        };

        ButtonColumn buttonColumn = new ButtonColumn(t, view, 3);
        buttonColumn.setMnemonic(KeyEvent.VK_D);
        
        return t;
    }
    public static class ButtonColumn extends AbstractCellEditor
            implements TableCellRenderer, TableCellEditor, ActionListener, MouseListener
    {
            private JTable table;
            private Action action;
            private int mnemonic;
            private Border originalBorder;
            private Border focusBorder;

            private JButton renderButton;
            private JButton editButton;
            private Object editorValue;
            private boolean isButtonColumnEditor;

            /**
             *  Create the ButtonColumn to be used as a renderer and editor. The
             *  renderer and editor will automatically be installed on the TableColumn
             *  of the specified column.
             *
             *  @param table the table containing the button renderer/editor
             *  @param action the Action to be invoked when the button is invoked
             *  @param column the column to which the button renderer/editor is added
             */
            public ButtonColumn(JTable table, Action action, int column)
            {
                    this.table = table;
                    this.action = action;

                    renderButton = new JButton();
                    editButton = new JButton();
                    editButton.setFocusPainted( false );
                    editButton.addActionListener( this );
                    originalBorder = editButton.getBorder();
                    setFocusBorder( new LineBorder(Color.BLUE) );

                    TableColumnModel columnModel = table.getColumnModel();
                    columnModel.getColumn(column).setCellRenderer( this );
                    columnModel.getColumn(column).setCellEditor( this );
                    table.addMouseListener( this );
                    table.setDefaultEditor(Action.class, this);
                    table.setDefaultRenderer(Action.class, this);
            }


            /**
             *  Get foreground color of the button when the cell has focus
             *
             *  @return the foreground color
             */
            public Border getFocusBorder()
            {
                    return focusBorder;
            }

            /**
             *  The foreground color of the button when the cell has focus
             *
             *  @param focusBorder the foreground color
             */
            public void setFocusBorder(Border focusBorder)
            {
                    this.focusBorder = focusBorder;
                    editButton.setBorder( focusBorder );
            }

            public int getMnemonic()
            {
                    return mnemonic;
            }

            /**
             *  The mnemonic to activate the button when the cell has focus
             *
             *  @param mnemonic the mnemonic
             */
            public void setMnemonic(int mnemonic)
            {
                    this.mnemonic = mnemonic;
                    renderButton.setMnemonic(mnemonic);
                    editButton.setMnemonic(mnemonic);
            }

            @Override
            public java.awt.Component getTableCellEditorComponent(
                    JTable table, Object value, boolean isSelected, int row, int column)
            {
                    if (value == null)
                    {
                            editButton.setText( "" );
                            editButton.setIcon( null );
                    }
                    else if (value instanceof Icon)
                    {
                            editButton.setText( "" );
                            editButton.setIcon( (Icon)value );
                    }
                    else
                    {
                            editButton.setText( value.toString() );
                            editButton.setIcon( null );
                    }

                    this.editorValue = value;
                    return editButton;
            }

            @Override
            public Object getCellEditorValue()
            {
                    return editorValue;
            }

    //
    //  Implement TableCellRenderer interface
    //
            public java.awt.Component getTableCellRendererComponent(
                    JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column)
            {
                    if (isSelected)
                    {
                            renderButton.setForeground(table.getSelectionForeground());
                            renderButton.setBackground(table.getSelectionBackground());
                    }
                    else
                    {
                            renderButton.setForeground(table.getForeground());
                            renderButton.setBackground(UIManager.getColor("Button.background"));
                    }

                    if (hasFocus)
                    {
                            renderButton.setBorder( focusBorder );
                    }
                    else
                    {
                            renderButton.setBorder( originalBorder );
                    }

    //		renderButton.setText( (value == null) ? "" : value.toString() );
                    if (value == null)
                    {
                            renderButton.setText( "" );
                            renderButton.setIcon( null );
                    }
                    else if (value instanceof Icon)
                    {
                            renderButton.setText( "" );
                            renderButton.setIcon( (Icon)value );
                    }
                    else
                    {
                            renderButton.setText( value.toString() );
                            renderButton.setIcon( null );
                    }

                    return renderButton;
            }

    //
    //  Implement ActionListener interface
    //
            /*
             *	The button has been pressed. Stop editing and invoke the custom Action
             */
            public void actionPerformed(ActionEvent e)
            {
                    int row = table.convertRowIndexToModel( table.getEditingRow() );
                    fireEditingStopped();

                    //  Invoke the Action

                    ActionEvent event = new ActionEvent(
                            table,
                            ActionEvent.ACTION_PERFORMED,
                            "" + row);
                    action.actionPerformed(event);
            }

    //
    //  Implement MouseListener interface
    //
            /*
             *  When the mouse is pressed the editor is invoked. If you then then drag
             *  the mouse to another cell before releasing it, the editor is still
             *  active. Make sure editing is stopped when the mouse is released.
             */
        public void mousePressed(MouseEvent e)
        {
            if (table.isEditing()
                    &&  table.getCellEditor() == this)
                            isButtonColumnEditor = true;
        }

        public void mouseReleased(MouseEvent e)
        {
            if (isButtonColumnEditor
            &&  table.isEditing())
                    table.getCellEditor().stopCellEditing();

                    isButtonColumnEditor = false;
        }

        public void mouseClicked(MouseEvent e) {}
            public void mouseEntered(MouseEvent e) {}
        public void mouseExited(MouseEvent e) {}
    }
    
    static class MultilineTableCell implements TableCellRenderer {
        class CellArea extends DefaultTableCellRenderer {
            /**
             * 
             */
            private static final long serialVersionUID = 1L;
            private String text;
            protected int rowIndex;
            protected int columnIndex;
            protected JTable table;
            protected java.awt.Font font;
            private int paragraphStart,paragraphEnd;
            private LineBreakMeasurer lineMeasurer;

            public CellArea(String s, JTable tab, int row, int column,boolean isSelected) {
                text = s;
                rowIndex = row;
                columnIndex = column;
                table = tab;
                font = table.getFont();
                if (isSelected) {
                    setForeground(table.getSelectionForeground());
                    setBackground(table.getSelectionBackground());
                }
            }
            public void paintComponent(java.awt.Graphics gr) {
                super.paintComponent(gr);
                if ( text != null && !text.isEmpty() ) {
                    Graphics2D g = (Graphics2D) gr;
                    if (lineMeasurer == null) {
                        AttributedCharacterIterator paragraph = new AttributedString(text).getIterator();
                        paragraphStart = paragraph.getBeginIndex();
                        paragraphEnd = paragraph.getEndIndex();
                        FontRenderContext frc = g.getFontRenderContext();
                        lineMeasurer = new LineBreakMeasurer(paragraph,BreakIterator.getWordInstance(), frc);
                    }
                    float breakWidth = (float)table.getColumnModel().getColumn(columnIndex).getWidth();
                    float drawPosY = 0;
                    // Set position to the index of the first character in the paragraph.
                    lineMeasurer.setPosition(paragraphStart);
                    // Get lines until the entire paragraph has been displayed.
                    while (lineMeasurer.getPosition() < paragraphEnd) {
                        // Retrieve next layout. A cleverer program would also cache
                        // these layouts until the component is re-sized.
                        TextLayout layout = lineMeasurer.nextLayout(breakWidth);
                        // Compute pen x position. If the paragraph is right-to-left we
                        // will align the TextLayouts to the right edge of the panel.
                        // Note: this won't occur for the English text in this sample.
                        // Note: drawPosX is always where the LEFT of the text is placed.
                        float drawPosX = layout.isLeftToRight()
                            ? 0 : breakWidth - layout.getAdvance();
                        // Move y-coordinate by the ascent of the layout.
                        drawPosY += layout.getAscent();
                        // Draw the TextLayout at (drawPosX, drawPosY).
                        layout.draw(g, drawPosX, drawPosY);
                        // Move y-coordinate in preparation for next layout.
                        drawPosY += layout.getDescent() + layout.getLeading();
                    }
                    table.setRowHeight(rowIndex,(int) drawPosY);
                }
            }
        }
        public java.awt.Component getTableCellRendererComponent(JTable table, Object value,boolean isSelected, boolean hasFocus, int row,int column
            )
        {
            if(value == null) {
                value = "";
            }
            CellArea area = new CellArea(value.toString(),table,row,column,isSelected);
            return area;
        }   
    }
    
    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JButton clearData;
    private javax.swing.JTree componentHierarchy;
    private javax.swing.JLabel imageMemory;
    private javax.swing.JLabel jLabel1;
    private javax.swing.JLabel jLabel2;
    private javax.swing.JPanel jPanel1;
    private javax.swing.JPanel jPanel2;
    private javax.swing.JScrollPane jScrollPane1;
    private javax.swing.JScrollPane jScrollPane2;
    private javax.swing.JScrollPane jScrollPane3;
    private javax.swing.JScrollPane jScrollPane4;
    private javax.swing.JScrollPane jScrollPane5;
    private javax.swing.JSplitPane jSplitPane1;
    private javax.swing.JTabbedPane jTabbedPane1;
    private javax.swing.JTable jTable1;
    private javax.swing.JToolBar jToolBar1;
    private javax.swing.JButton pauseContinue;
    private javax.swing.JTextArea performanceLog;
    private javax.swing.JButton refreshFrame;
    private javax.swing.JTable renderedItems;
    private javax.swing.JTable resultData;
    private javax.swing.JButton runGC;
    // End of variables declaration//GEN-END:variables
}
